package com.gitee.apanlh.util.compression;

import com.aayushatharva.brotli4j.Brotli4jLoader;
import com.aayushatharva.brotli4j.decoder.BrotliInputStream;
import com.aayushatharva.brotli4j.encoder.BrotliOutputStream;
import com.gitee.apanlh.exp.FileOutException;
import com.gitee.apanlh.exp.zip.ZipCompressException;
import com.gitee.apanlh.exp.zip.ZipDecompressException;
import com.gitee.apanlh.util.base.Empty;
import com.gitee.apanlh.util.base.StringUtils;
import com.gitee.apanlh.util.check.CheckImport;
import com.gitee.apanlh.util.check.CheckLibrary;
import com.gitee.apanlh.util.encode.Base64Utils;
import com.gitee.apanlh.util.encode.CharsetCode;
import com.gitee.apanlh.util.encode.StrEncodeUtils;
import com.gitee.apanlh.util.file.FileUtils;
import com.gitee.apanlh.util.io.FileIOUtils;
import com.gitee.apanlh.util.io.IOUtils;
import com.gitee.apanlh.util.unit.BuffSize;
import net.jpountz.lz4.LZ4BlockOutputStream;
import net.jpountz.lz4.LZ4Compressor;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.lz4.LZ4FastDecompressor;
import org.xerial.snappy.Snappy;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.Inflater;

/**	
 * 	数据压缩
 * 
 * 	@author Pan
 */
public class ZipUtils {
	
	static {
		CheckImport.library(CheckLibrary.ZIP_LZ4);
		CheckImport.library(CheckLibrary.ZIP_BROTLI);
		CheckImport.library(CheckLibrary.ZIP_SNAPPY);
	}
	
	/**
	 * 	构造函数
	 * 
	 * 	@author Pan
	 */
	private ZipUtils() {
		//	不允许外部实例
		super();
	}
	
	/**	
	 * 	压缩
	 * 	
	 * 	@author Pan
	 * 	@param 	str		原内容
	 * 	@return	byte[]
	 */
	public static byte[] compress(String str) {
		return compress(StrEncodeUtils.utf8EncodeToBytes(str));
	}
	
	/**
	 * 	压缩
	 * 	
	 * 	@author Pan
	 * 	@param 	content		原内容
	 * 	@return	byte[]
	 */
	public static byte[] compress(byte[] content) {
		ByteArrayOutputStream baos = null;
		GZIPOutputStream gos = null;
		try {
			baos = new ByteArrayOutputStream(BuffSize.SIZE_8K);
			gos = new GZIPOutputStream(baos, BuffSize.SIZE_8K);
			IOUtils.write(content, gos, BuffSize.SIZE_8K);
			gos.finish();
			gos.flush();
		} catch (Exception e) {
			throw new ZipCompressException(e);
		} finally {
			IOUtils.close(gos, baos);
		}
		return baos.toByteArray();
	}
	
	/**	
	 * 	压缩文件
	 * 	<br>文件名原来一致
	 * 	<br>会把原有文件覆盖
	 * 
	 * 	@author Pan
	 * 	@param 	filePath	需要压缩的源文件地址
	 */
	public static void compressToFile(String filePath) {
		compressToFile(new File(filePath));
	}
	
	/**
	 * 	压缩文件
	 * 	<br>文件名原来一致
	 * 	<br>会把原有文件覆盖
	 * 	
	 * 	@author Pan
	 * 	@param 	file		文件对象
	 */
	public static void compressToFile(File file) {
		if (file == null) {
			return ;
		}
		compressToFile(file, file.getPath());
	}
	
	/**
	 * 	压缩文件
	 * 	<br>文件名原来一致
	 * 	<br>会把原有文件覆盖
	 * 
	 * 	@author Pan
	 * 	@param 	file			文件
	 * 	@param 	outPath			压缩输出地址
	 */
	public static void compressToFile(File file, String outPath) {
		if (file == null) {
			return ;
		}
		compressToFile(file, outPath, "");
	}
	
	/**
	 * 	压缩方法
	 * 	<br>添加后缀名
	 * 	
	 * 	@author Pan
	 * 	@param 	file			文件
	 * 	@param 	outPath			压缩输出地址
	 * 	@param 	suffix			压缩后文件名称
	 */
	public static void compressToFile(File file, String outPath, String suffix) {
		if (file == null) {
			return ;
		}
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(file);
			outLocal(fis, outPath, file.getName(), suffix);
		} catch (Exception e) {
			throw new ZipCompressException(e);
		} finally {
			IOUtils.close(fis);
		}
	}
	
	/**	
	 * 	压缩(Deflater)
	 * 	<br>默认不添加校验头规则
	 * 	<br>默认压缩等级1
	 * 	<br>如果压缩无开启校验头则解压也必须未开启校验头
	 * 	
	 * 	@author Pan
	 * 	@param 	content	原内容
	 * 	@return	byte[]
	 */
	public static byte[] compressDeflater(String content) {
		return compressDeflater(content, 1, true);
	}
	
	/**	
	 * 	压缩(Deflater)	
	 * 	<br>默认不添加校验头规则
	 * 	<br>自定义压缩等级1-9
	 * 	
	 * 	@author Pan
	 * 	@param 	content		原内容
	 * 	@param 	level		压缩等级1-9
	 * 	@return	byte[]
	 */
	public static byte[] compressDeflater(String content, int level) {
		return compressDeflater(content, level, true);
	}
	
	/**
	 * 	压缩(Deflater)
	 * 	
	 * 	@author Pan
	 * 	@param 	content		原内容
	 * 	@param 	level		压缩等级1-9
	 * 	@param 	nowrap		是否增加校验头
	 * 	@return	byte[]
	 */
	public static byte[] compressDeflater(String content, int level, boolean nowrap) {
		return compressDeflater(StrEncodeUtils.utf8EncodeToBytes(content), level, nowrap);
	}
	
	/**	
	 * 	压缩(Deflater)
	 * 	<br>默认不添加校验头规则
	 * 	<br>默认压缩等级1
	 * 	<br>如果压缩无开启校验头则解压也必须未开启校验头
	 * 	
	 * 	@author Pan
	 * 	@param 	content	内容
	 * 	@return	byte[]
	 */
	public static byte[] compressDeflater(byte[] content) {
		return compressDeflater(content, 1, true);
	}
	
	/**	
	 * 	压缩(Deflater)	
	 * 	<br>默认不添加校验头规则
	 * 	<br>自定义压缩等级1-9
	 * 	
	 * 	@author Pan
	 * 	@param 	content		原内容
	 * 	@param 	level		压缩等级1-9
	 * 	@return	byte[]
	 */
	public static byte[] compressDeflater(byte[] content, int level) {
		return compressDeflater(content, level, true);
	}
	
	/**
	 * 	压缩(Deflater)
	 * 	
	 * 	@author Pan
	 * 	@param 	content		原内容
	 * 	@param 	level		压缩等级1-9
	 * 	@param 	nowrap		是否增加校验头
	 * 	@return	byte[]
	 */
	public static byte[] compressDeflater(byte[] content, int level, boolean nowrap) {
	    Deflater compress = null;
	    ByteArrayOutputStream bos = new ByteArrayOutputStream(BuffSize.SIZE_8K);
	    try {
	    	compress = new Deflater(level, nowrap);
	    	compress.reset();
	    	compress.setInput(content);
	    	compress.finish();
		    
	    	byte[] buffer = new byte[BuffSize.SIZE_1K];
	        while (!compress.finished()) {
	            int count = compress.deflate(buffer);
	            bos.write(buffer, 0, count);
	        }
	    } catch (Exception e) {
	    	throw new ZipCompressException(e);
	    } finally {
	    	if (compress != null) {
	    		compress.end();
	    	}
	        IOUtils.close(bos);
	    }
	    return bos.toByteArray();
	}
	
	/**
	 * 	压缩(Snappy)
	 * 	
	 * 	@author Pan
	 * 	@param 	content		原内容
	 * 	@return	byte[]
	 */
	public static byte[] compressSnappy(byte[] content) {
		try {
			return Snappy.compress(content);
		} catch (Exception e) {
			throw new ZipCompressException(e);
		}
	}
	
	/**
	 * 	压缩(Snappy)
	 * 	默认UTF-8格式
	 * 	
	 * 	@author Pan
	 * 	@param 	content		原内容
	 * 	@return	byte[]
	 */
	public static byte[] compressSnappy(String content) {
		try {
			return Snappy.compress(content, CharsetCode.UTF_8);
		} catch (Exception e) {
			throw new ZipCompressException(e);
		}
	}
	/**
	 * 	压缩(lz4)
	 * 	
	 * 	@author Pan
	 * 	@param 	content		原内容
	 * 	@return	byte[]
	 */
	public static byte[] compressLz4(byte[] content) {
		try {
			LZ4Factory factory = LZ4Factory.fastestInstance();
	        LZ4Compressor compressor = factory.fastCompressor();
	        return compressor.compress(content);
		} catch (Exception e) {
			throw new ZipCompressException(e);
		}
	}
	
	/**
	 * 	压缩(lz4)
	 * 	默认UTF-8格式
	 * 	
	 * 	@author Pan
	 * 	@param 	content		原内容
	 * 	@return	byte[]
	 */
	public static byte[] compressLz4(String content) {
		try {
			return compressLz4(StrEncodeUtils.utf8EncodeToBytes(content));
		} catch (Exception e) {
			throw new ZipCompressException(e);
		}
	}
	
	/**
	 * 	压缩(lz4)
	 * 	
	 * 	@author Pan
	 * 	@param 	content			原内容
	 * 	@param 	blockSize		一次压缩的大小 取值范围 64 字节-32M之间
	 * 	@return	byte[]
	 */
	public static byte[] compressLz4Block(byte[] content, int blockSize) {
		ByteArrayOutputStream baos = null;
		LZ4BlockOutputStream compressedOutput = null;
		try {
			LZ4Factory factory = LZ4Factory.fastestInstance();
			
			baos = new ByteArrayOutputStream(BuffSize.SIZE_8K);
	        LZ4Compressor compressor = factory.fastCompressor();
	        compressedOutput = new LZ4BlockOutputStream(baos, blockSize, compressor);
	        compressedOutput.write(content);
	        
	        return baos.toByteArray();
		} catch (Exception e) {
			throw new ZipCompressException(e);
		} finally {
			IOUtils.close(compressedOutput, baos);
		}
	}
	
	/**
	 * 	压缩(Brotli)
	 * 	
	 * 	@author Pan
	 * 	@param 	content	内容
	 * 	@return	byte[]
	 */
	public static byte[] compressBrotli(byte[] content) {
		if (content == null) {
			return Empty.arrayByte();
		}

		Brotli4jLoader.getUnavailabilityCause();
		ByteArrayOutputStream os = null;
		BrotliOutputStream brotliOs = null;
		try {
			os = new ByteArrayOutputStream();
			brotliOs = new BrotliOutputStream(os);
			IOUtils.write(content, brotliOs);
			return os.toByteArray();
		} catch (Exception e) {
			throw new ZipCompressException(e.getMessage(), e);
		} finally {
			IOUtils.close(brotliOs, os);
		}
	}
	
	/**
	 * 	压缩(Brotli)
	 * 	
	 * 	@author Pan
	 * 	@param 	content	内容
	 * 	@return	byte[]
	 */
	public static byte[] compressBrotli(String content) {
		if (content == null) {
			return Empty.arrayByte();
		}
		return compressBrotli(content.getBytes());
	}
	
	/**
	 * 	压缩(Brotli)
	 * 	<br>Base64格式
	 * 	<br>返回二进制内容
	 * 	
	 * 	@author Pan
	 * 	@param 	content	内容
	 * 	@return	String
	 */
	public static String compressBrotliToBase64(String content) {
		return Base64Utils.encodeToStr(compressBrotli(content));
	}
	
	/**	
	 * 	解压缩
	 * 		
	 * 	@author Pan
	 * 	@param 	content		 已被压缩的内容
	 * 	@return	byte[]
	 */
	public static byte[] decompress(byte[] content) {
		ByteArrayOutputStream baos = null;
		GZIPInputStream gis = null;
		try {
			gis = new GZIPInputStream(IOUtils.toByteInput(content), BuffSize.SIZE_8K);
			baos = new ByteArrayOutputStream(BuffSize.SIZE_8K);
			IOUtils.copy(gis, baos, true);
			return baos.toByteArray();
		} catch (Exception e) {
			throw new ZipDecompressException(e);
		} finally {
			IOUtils.close(baos, gis);
		}
	}
	
	/**
	 * 	解压缩
	 * 	
	 * 	@param 	content		已被压缩的内容
	 * 	@return	String
	 */
	public static String decompressToStr(byte[] content) {
		return StrEncodeUtils.utf8EncodeToStr(decompress(content));
	}
	
	/**
	 * 	解压缩
	 * 	<br>解压后名称默认压缩名
	 * 	
	 * 	@author Pan
	 * 	@param 	file			压缩文件
	 */
	public static void decompressToFile(File file) {
		decompressToFile(file, file.getName());
	}
	
	/**	
	 * 	解压缩
	 * 	<br>自定义解压缩名
	 * 	
	 * 	@author Pan
	 * 	@param 	file			压缩文件
	 * 	@param 	targetFileName	解压后名称
	 */
	public static void decompressToFile(File file, String targetFileName) {
		FileInputStream fis = null;
		GZIPInputStream gis = null;
		ByteArrayOutputStream baos = null;
		try {
			fis = new FileInputStream(file);
			gis = new GZIPInputStream(fis, BuffSize.SIZE_8K);
			baos = new ByteArrayOutputStream(BuffSize.SIZE_8K);
			IOUtils.copy(gis, baos, BuffSize.SIZE_8K);
			
			String currentFileDir = FileUtils.getFilePath(file);
			FileIOUtils.writeToFile(baos.toByteArray(), new File(currentFileDir.concat(targetFileName)));
		} catch (Exception e) {
			throw new ZipDecompressException(e);
		} finally {
			IOUtils.close(fis, gis, baos);
		}
	}
	
	/**	
	 * 	解压缩(Deflater)
	 * 	<br> 默认取消校验头规则
	 * 	
	 * 	@author Pan
	 * 	@param 	content		 已被压缩的内容
	 * 	@return	byte[]
	 */
	public static byte[] decompressDeflater(byte[] content) {
		return decompressDeflater(content, true);
	}
	
	/**	
	 * 	解压缩(Deflater)
	 * 	
	 * 	@author Pan
	 * 	@param 	content		 已被压缩的内容
	 * 	@param 	nowrap		 是否开启校验头
	 * 	@return	byte[]
	 */
	public static byte[] decompressDeflater(byte[] content, boolean nowrap) {
	    ByteArrayOutputStream baos = null;
	    Inflater inflater = null;
	    try {
	    	baos = new ByteArrayOutputStream(BuffSize.SIZE_8K);
	    	
	    	inflater = new Inflater(nowrap);
	    	inflater.reset();
		    inflater.setInput(content);
		    
	    	byte[] buffer = new byte[BuffSize.SIZE_1K];
	  	    while (!inflater.finished()) {
	  	    	int count = inflater.inflate(buffer);
	  	    	baos.write(buffer, 0, count);
	  	    }
		} catch (Exception e) {
			throw new ZipDecompressException(e);
		} finally {
			if (inflater != null) {
				inflater.end();
			}
			IOUtils.close(baos);
		}
	    return baos.toByteArray();
	}
	
	/**	
	 * 	解压缩(Deflater)
	 * 	<br>默认取消检验头校验
	 * 	
	 * 	@author Pan
	 * 	@param 	content		已被压缩的内容
	 * 	@return	String
	 */
	public static String decompressDeflaterToStr(byte[] content) {
		return decompressDeflaterToStr(content, true);
	}
	
	/**	
	 * 	解压缩(Deflater)	
	 * 
	 * 	@author Pan
	 * 	@param 	content		已被压缩的内容
	 * 	@param 	nowrap		解压缩时是否检验头校验
	 * 	@return	String
	 */	
	public static String decompressDeflaterToStr(byte[] content, boolean nowrap) {
		return StrEncodeUtils.utf8EncodeToStr(decompressDeflater(content, nowrap));
	}
	
	/**	
	 * 	解压缩(Snappy)	
	 * 
	 * 	@author Pan
	 * 	@param 	content		已被压缩的内容
	 * 	@return	byte[]
	 */	
	public static byte[] decompressSnappy(byte[] content) {
		try {
			return Snappy.uncompress(content);
		} catch (Exception e) {
			throw new ZipDecompressException(e);
		}
	}
	
	/**	
	 * 	解压缩(Snappy)	
	 * 
	 * 	@author Pan
	 * 	@param 	content		已被压缩的内容
	 * 	@return	String
	 */
	public static String decompressSnappyToStr(byte[] content) {
		try {
			return Snappy.uncompressString(content, CharsetCode.UTF_8);
		} catch (Exception e) {
			throw new ZipDecompressException(e);
		}
	}
	
	/**	
	 * 	解压缩(Lz4)
	 * 	
	 * 	@author Pan
	 * 	@param 	content		 压缩后的数据的内容
	 * 	@param 	srcLen		 原始数据长度(byte[]长度)
	 * 	@return	byte[]
	 */
	public static byte[] decompressLz4(byte[] content, int srcLen) {
		try {
			LZ4Factory factory = LZ4Factory.fastestInstance();
	        LZ4FastDecompressor decompressor = factory.fastDecompressor();
	        return decompressor.decompress(content, srcLen);
		} catch (Exception e) {
			throw new ZipDecompressException(e);
		} 
	}
	
	/**	
	 * 	解压缩(Lz4)
	 * 	<br>返回字符串
	 * 	
	 * 	@author Pan
	 * 	@param 	content		 压缩后的数据的内容
	 * 	@param 	srcLen		 压缩前的数据长度
	 * 	@return	String
	 */
	public static String decompressLz4ToStr(byte[] content, int srcLen) {
		return StrEncodeUtils.utf8EncodeToStr(decompressLz4(content, srcLen));
	}
	
	/**
	 * 	解压缩(Brotli)
	 * 	
	 * 	@author Pan
	 * 	@param 	content	原内容
	 * 	@return	String
	 */
	public static String decompressBrotli(byte[] content) {
		if (content == null) {
			return null;
		}
		ByteArrayInputStream bais = null;
		BrotliInputStream bis = null;
		
		try {
			bais = new ByteArrayInputStream(content);
			bis = new BrotliInputStream(bais);
			
			return IOUtils.readString(bis);
		} catch (Exception e) {
			throw new ZipDecompressException(e);
		} finally {
			IOUtils.close(bis, bais);
		}
	}
	
	/**
	 * 	解压缩(Brotli)
	 * 	<br>解压Base64格式
	 * 	
	 * 	@author Pan
	 * 	@param 	base64Content	base64内容
	 * 	@return	String
	 */
	public static String decompressBrotliByBase64(String base64Content) {
		if (base64Content == null) {
			return null;
		}
		return decompressBrotli(Base64Utils.decode(base64Content));
	}
	
	/**
	 * 	压缩后落地至本地
	 * 	
	 * 	@author Pan
	 * 	@param 	is				输入流
	 * 	@param 	outPath			压缩输出地址
	 * 	@param 	outName			压缩包名称
	 * 	@param 	suffix			后缀
	 */
	private static void outLocal(InputStream is, String outPath, String outName, String suffix) {
		ByteArrayOutputStream baos = null;
		GZIPOutputStream gos = null;
		
		try {
			baos = new ByteArrayOutputStream(BuffSize.SIZE_8K);
			gos = new GZIPOutputStream(baos, BuffSize.SIZE_8K);
			IOUtils.copy(is, gos, BuffSize.SIZE_8K);
			gos.finish();
			gos.flush();
			FileIOUtils.writeToFile(baos.toByteArray(), new File(outPath.concat(outName).concat(suffix)));
		} catch (Exception e) {
			throw new FileOutException(StringUtils.format("zip out local error cause:{}", "{}"), e);
		} finally {
			IOUtils.close(is, baos, gos);
		}
	}
}
